#ifndef HELLOTRIANGLEAPPLICATION_H
#define HELLOTRIANGLEAPPLICATION_H

//#define ManualCreateSurface   // 手动平台相关创建渲染显示表面
#ifdef ManualCreateSurface
//  要像这样定义才能使用非封装方法创建窗体和渲染表面
//  这里只是vulkan 表面创建示例用的， 平台无关glfw glfwCreateWindowSurface函数可实现这些功能
#define VK_USE_PLATFORM_WIN32_KHR
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>

#else
//#include <vulkan/vulkan.h>
#define GLFW_INCLUDE_VULKAN  // #include <vulkan/vulkan.h>
#include "GLFW/glfw3.h"

#endif

#define GLM_FORCE_RADIANS   // 强制glm库使用弧度
#include <glm/gtc/matrix_transform.hpp>
#include <chrono>  //  高精度计时库

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE  //  由于vulkan与opengl深度范围不一样，它是0~1.0的
#include "glm/glm.hpp"
#include <glm/gtc/matrix_transform.hpp>

#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <stdlib.h>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <fstream>
#include <array>

class HelloTriangleApplication {
public:

    HelloTriangleApplication();

    ~HelloTriangleApplication();

    void run();

    static std::vector<char> readFile(const std::string& filename) {
        std::ifstream file(filename, std::ios::ate | std::ios::binary);

        if (!file.is_open()) {
            throw std::runtime_error("failed to open file!");
        }

        size_t fileSize = static_cast<size_t>(file.tellg());
        std::vector<char> buffer(fileSize);
        file.seekg(0);
        file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
        file.close();
        return buffer;
    }

private:

    struct SwapChainSupportDetails {  // 交换链一些特性
        VkSurfaceCapabilitiesKHR capabilities;  //  队列中图像的数量、长宽限制等
        std::vector<VkSurfaceFormatKHR> formats;    //  支持的像素格式、颜色空间等
        std::vector<VkPresentModeKHR> presentModes;  //  刷新显示图像的模式,例如(FIFO、双缓冲等)
    };

    GLFWwindow * m_window;  //  窗体成员对象

    VkInstance m_instance;  // vulkan实例，应用程序通过它告诉vulkan库和驱动本应用的一些信息

    VkDebugUtilsMessengerEXT m_debugMessenger;  //  调试信息对象

    VkPhysicalDevice m_physicalDevice;  // 物理显卡设备句柄, 不用显式释放（它会在vulkan实例销毁时自动隐式释放）

    enum class QueFamiliesKey{
        Graphics,
        SurfaceSupportKHR,
    };
    std::map<QueFamiliesKey, uint32_t> m_devQueFamilies;    // 显卡可用的驱动队列簇

    VkDevice m_device;      //  逻辑设备，物理设备的抽象，用于创建立操作队列(与物理设备交互)

    VkQueue m_graphicsQueue;    //  创建逻辑设备一同会创建驱动队列，使用逻辑设备可查找到驱动队列句柄， 不用显式释放它(相应的逻辑设备释放时，它会自动释放)
    VkQueue m_presentQueue;     //  显示队列，注意显示队列与图形队列可能并不会同时在一个队列家族中可用

    // VkSurfaceKHR对象是平台无关的，但创建过程就要依赖于运行平台，
    // 例如Windows平台，需要创建实例时声明使用VK_KHR_win32_surface扩展属性
    VkSurfaceKHR m_abs_surface;  //  抽象渲染结果（图像）的显示表面， 创建具有VK_KHR_surface扩展属性的实例可以使用这种抽象对象用于显示


    VkSwapchainKHR m_swapChain;  //  swap chain

    VkFormat m_swapChainImageFormat;    //  交换链图像的格式
    VkExtent2D m_swapChainExtent;       //  交换链图像的大小
    std::vector<VkImage> m_swapChainImages;     //  交换链中图像对象数组, 交换链创建时自动创建， 交换链销毁时自动销毁
    std::vector<VkImageView> m_swapChainImageViews;   //  交换链中图像对象对应的图像访问视口, 需要在交换链销毁前显式销毁

    VkRenderPass m_renderPass;            //  渲染通道， 类似于如何处理帧缓冲对象的附件的概念, 需要手动销毁
    VkDescriptorSetLayout m_descriptorSetLayout;  //  描述符集布局对象, 需要手动销毁
    VkDescriptorPool m_descriptorPool;            //  描述符池,需要手动销毁
    std::vector<VkDescriptorSet> m_descriptorSets;    //  描述符集, 不需要手动销毁， 描述符池释放时它自动释放
    VkPipelineLayout m_pipelineLayout;  //  管线布局， 用于着色器uniform布局输入的变更, 需要手动销毁
    VkPipeline m_graphicsPipeline;    //  管线对象句柄, 需要手动销毁

    std::vector<VkFramebuffer> m_swapChainFramebuffers;  //  帧缓冲数组。多个交换链交换图像对应多个帧缓冲。每个帧缓冲引用图像视口作为附件。需要手动销毁

    VkCommandPool m_commandPool;   //  命令池，用于存储命令和管理命令缓冲, 需要手动销毁

    std::vector<VkCommandBuffer> m_commandBuffers;  //  命令缓冲，可多线程把命令提交到命令池中。命令池销毁时会自动释放。

    std::vector<VkSemaphore> m_imageAvailableSemaphores;   //  从交换链获取到图像信号量， 用于gpu则执行同步, 需要手动销毁
    std::vector<VkSemaphore> m_renderFinishedSemaphores;   //  渲染完成信号量，用于gpu则执行同步, 需要手动销毁
    std::vector<VkFence> m_inFlightFences;      //  单帧渲染显示完成栅栏， 用于cpu等待gpu执行完成, 需要手动销毁

    bool m_framebufferResized;  //  标记帧缓冲对象是否已经改变了大小(显示窗口大小变化标记)

    uint32_t m_currentFrame;  //  当前正在渲染的帧索引， 用于多个命令缓冲异步渲染不同的帧

    struct Vertex {
        glm::vec3 pos;
        glm::vec3 color;
        glm::vec2 texCoord;

        /**
         * @brief 返回顶点数据结构绑定的数组索引、一个顶点数据结构的字节长度、顶点数据结构是实例还是逐顶点
         * @return
         */
        static VkVertexInputBindingDescription getBindingDescription() {
            VkVertexInputBindingDescription bindingDescription{};

            bindingDescription.binding = 0;     //  绑定数组中绑定的索引
            bindingDescription.stride = sizeof(Vertex);  // 每两个顶点数据结构间的内存间隔
            //VK_VERTEX_INPUT_RATE_VERTEX: 画完一个顶点就移动一次数据结束
            //VK_VERTEX_INPUT_RATE_INSTANCE: 画完一个实例就移动一次数据结束
            bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
            return bindingDescription;
        }

        /**
         * @brief 返回顶点数据结构的顶点坐标和对应的其它属性信息
         * @return
         */
        static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
            std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};

            attributeDescriptions[0].binding = 0;
            attributeDescriptions[0].location = 0;   //  绑定的位置 layout(location = x) 的x
            attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;  //  格式
            attributeDescriptions[0].offset = offsetof(Vertex, pos);    //  属性相对于结构数据的字节偏移

            attributeDescriptions[1].binding = 0;
            attributeDescriptions[1].location = 1;
            attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
            attributeDescriptions[1].offset = offsetof(Vertex, color);

            attributeDescriptions[2].binding = 0;
            attributeDescriptions[2].location = 2;
            attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
            attributeDescriptions[2].offset = offsetof(Vertex, texCoord);

            return attributeDescriptions;
        }
    };

    struct UniformBufferObject {
        alignas(16) glm::mat4 model;    // 显式指定地址对齐， 减少与glsl uniform结构体数据对齐不一致的问题
        alignas(16) glm::mat4 view;
        alignas(16) glm::mat4 proj;
    }; //  统一缓冲对象数据结构， 常用于着色器可访问的一致模型、视图、投影常量

    const std::vector<Vertex> m_vertices = {
        {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
        {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
        {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
        {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},

        {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
        {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
        {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
        {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}
    };  //  顶点及其属性数据

    const std::vector<uint16_t> m_indices = {
        0, 1, 2, 2, 3, 0,
        4, 5, 6, 6, 7, 4
    };  //  顶点索引数据

    //  图像缓冲对象
    VkImage m_textureImage = VK_NULL_HANDLE;  // 图像数据缓冲对象, 需要显式释放
    VkDeviceMemory m_textureImageMemory = VK_NULL_HANDLE;  // 图像数据缓冲对象分配到的显存, 需要显式释放
    VkImageView m_textureImageView = VK_NULL_HANDLE;  //  纹理图像视图
    VkSampler m_textureSampler = VK_NULL_HANDLE;  //  纹理图像采样器

    // 深度缓冲对象
    VkImage m_depthImage;
    VkDeviceMemory m_depthImageMemory;
    VkImageView m_depthImageView;


    VkBuffer m_vertexBuffer; // 顶点数据缓冲对象, 需要显式释放
    VkDeviceMemory m_vertexBufferMemory;  // 顶点数据缓冲对象分配到的显存, 需要显式释放
    VkBuffer m_indexBuffer;  // 索引缓冲对象
    VkDeviceMemory m_indexBufferMemory;  // 索引缓冲对象分配到的显存

    std::vector<VkBuffer> m_uniformBuffers;  //  统一缓冲对象，每个渲染帧流对应一个
    std::vector<VkDeviceMemory> m_uniformBuffersMemory;  //  统一缓冲对象的显存，每个渲染帧流对应一个

    void initWindow();

    /**
     * @brief 检查vulkan是否支持需要的校验层
     * @return  支持返回true
     */
    bool checkValidationLayerSupport();

    /**
     * @brief 创建vulkan实例
     */
    void createInstance();

    /**
     * @brief 设置调试功能消息回调函数
     */
    void setupDebugMessenger();

    /**
     * @brief 创建渲染显示的表面(屏幕抽象)，一般应在创建vulkan实例后立即创建表面以免影响物理设备(显卡)的选择
     */
    void createSurface();

    /**
     * @brief 查询swap chain支持的一些信息明细
     * @param device 物理设备句柄
     * @return  返回swap chain支持的一些信息明细结构
     */
    SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);

    /**
     * @brief 选择支持vulkan实现的物理设备-显卡
     */
    void pickPhysicalDevice();

    /**
     * @brief 创建逻辑设备
     */
    void createLogicalDevice();

    /**
     * @brief 创建显示交换链
     */
    void createSwapChain();

    /**
     * @brief 为指定的图像缓冲对象创建图像缓冲
     * @param image  指定的图像
     * @param format  数据格式
     * @param aspectFlags  图像用途
     */
    VkImageView  createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags);

    /**
     * @brief 创建交换链的缓冲图像的图像访问视口
     */
    void createImageViews();

    /**
     * @brief 创建纹理图像视图
     */
    void createTextureImageView();

    /**
     * @brief 创建纹理采样器
     */
    void createTextureSampler();

    /**
     * @brief 创建渲染通道， 用于告诉vulkan帧缓冲的附件相关信息，例如使用多少个颜色或深度附件、使用多少重采样、在渲染过程中如何处理
     */
    void createRenderPass();

    /**
     * @brief 创建描述符集布局
     */
    void createDescriptorSetLayout();

    /**
     * @brief 创建图形处理管线
     */
    void createGraphicsPipeline();

    /**
     * @brief 创建帧缓冲
     */
    void createFramebuffers();
    /**
     * @brief 创建命令池
     */
    void createCommandPool();

    /**
     * @brief 从命令池中分配一个命令缓冲， 作为临时命令缓冲
     * @return
     */
    VkCommandBuffer beginSingleTimeCommands();

    /**
     * @brief 发送命令缓冲，等待并销毁命令缓冲
     * @param commandBuffer
     */
    void endSingleTimeCommands(VkCommandBuffer commandBuffer);

    /**
     * @brief 查找适合的显存类型
     * @param typeFilter: 参考VkMemoryRequirements 的memoryTypeBits成员
     * @param properties : 可指定VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT主机可见  VK_MEMORY_PROPERTY_HOST_COHERENT_BIT一致性等属性
     * @return
     */
    uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);

    /**
     * @brief 创建通用缓冲区对象
     * @param size  要创建的缓冲区的大小
     * @param usage 要创建的缓冲区的用途
     * @param properties 希望的显存属性
     * @param buffer  返回缓冲区对象
     * @param bufferMemory  返回与缓冲区对象绑定的显存
     */
    void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
                      VkMemoryPropertyFlags properties,
                      VkBuffer& buffer, VkDeviceMemory& bufferMemory
                      );

    /**
     * @brief 通用缓冲区对象内存复制
     * @param srcBuffer 源缓冲区对象
     * @param dstBuffer 目标缓冲区对象
     * @param size  要复制的大小(字节)
     */
    void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);

    /**
     * @brief 图像缓冲对象布局转换
     * @param image  图像缓冲对象
     * @param format 图像缓冲对象的数据格式
     * @param oldLayout  图像缓冲对象旧的布局
     * @param newLayout  图像缓冲对象新的布局
     */
    void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout);

    /**
     * @brief 复制缓冲对象内容到图像缓冲对象中
     * @param buffer
     * @param image
     * @param width
     * @param height
     */
    void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height);

    /**
     * @brief 创建图像缓冲对象和分配相应显存，并绑
     * @param width  宽
     * @param height  高
     * @param format 数据格式和排列
     * @param tiling    是否分方片排列
     * @param usage     用途
     * @param properties  显存特性
     * @param image     返回图像缓冲对象
     * @param imageMemory  返回图像缓冲对象对应显存
     */
    void createImage(uint32_t width,
                     uint32_t height,
                     VkFormat format, VkImageTiling tiling,
                     VkImageUsageFlags usage,
                     VkMemoryPropertyFlags properties,
                     VkImage& image, VkDeviceMemory& imageMemory);

    /**
     * @brief 创建深度缓冲相关资源
     */
    void createDepthResources();

    /**
     * @brief 创建图像纹理对象并分配内存，读入一张示例图片, 应保证调用前命令池已经创建
     */
    void createTextureImage();

    /**
     * @brief 创建顶点数据缓冲对象
     */
    void createVertexBuffer();

    /**
     * @brief 创建索引缓冲对象及分配其显存
     */
    void createIndexBuffer();

    /**
     * @brief 创建统一缓冲对象
     */
    void createUniformBuffers();

    /**
     * @brief 创建描述符池
     */
    void createDescriptorPool();

    /**
     * @brief 创建描述符集
     */
    void createDescriptorSets();

    /**
     * @brief 创建命令缓冲
     */
    void createCommandBuffer();

    /**
     * @brief 录入一条命令到命令缓冲
     * @param commandBuffer  命令缓冲
     * @param imageIndex  渲染的目标图像索引
     */
    void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex);

    /**
     * @brief 创建一些用于同步的对象
     */
    void createSyncObjects();

    void initVulkan();

    /**
     * @brief 更新统一缓冲对象
     * @param currentImage  当前的swap chain图像索引
     */
    void updateUniformBuffer(uint32_t currentImage);

    /**
     * @brief 画一帧
     */
    void drawFrame();

    /**
     * @brief 重新创建交换链
     */
    void recreateSwapChain();

    void mainLoop();

    void cleanup();
};

#endif // HELLOTRIANGLEAPPLICATION_H
